﻿// Copyright (C) Jari Hautio <jari.hautio@iki.fi> 2008. Licensed under GPLv2. See LICENSE.txt in solution folder.
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace ColorLib
{
    /// <summary>
    /// Color Cube is a 3 dimensional search structure for quickly finding
    /// nearest colors.
    /// </summary>
    public class GenericColorCube<T> : IEnumerable<KeyValuePair<Color, T>>, ICloneable
    {
        /// <summary>
        /// Size if internal color cube array
        /// </summary>
        protected int cubeSize = 0;
        public int CubeSize { get { return cubeSize; } }
		/// <summary>
		/// Internal data nodes stored in three dimensional array
		/// </summary>
		protected List<List<List<ColorNode>>> cube;

		protected int count=0;
		public int Count { get { return count; } }
		/// <summary>
        /// Color node contains list of colors inside cube item.
        /// </summary>
        public class ColorNode
        {
            Dictionary<Color, T> colors = null;
			public Dictionary<Color, T> Colors { get { if (colors == null) { colors = new Dictionary<Color, T>(); } return colors; } }
            public int Count { get { return colors != null ? colors.Count : 0;} }
			public void Add(Color c, T o)
			{
				if (colors == null)
					colors = new Dictionary<Color, T>();
				colors.Add(c, o);
			}
			public bool TryGetValue(Color c, ref T ob)
			{
				if (colors == null) return false;
				return colors.TryGetValue(c, out ob);
			}
			public bool ContainsKey(Color c)
			{
				if (colors == null) return false;
				return colors.ContainsKey(c);
			}
			public bool Remove(Color c)
            {
                if (colors != null)
                    return colors.Remove(c);
                return false;

            }
			public void Clear() { if (colors != null) { colors.Clear(); } }
			public Dictionary<Color, T> CloneColors()
			{
				return colors != null ? new Dictionary<Color, T>(colors) : null;
			}


			internal void InitColors(Dictionary<Color, T> dictionary)
			{
				if (dictionary != null)
					colors = new Dictionary<Color,T>(dictionary);
			}
		};
        /// <summary>
        /// Constructs Colorcube with default size
        /// </summary>
        public GenericColorCube()
        {
            cubeSize = 16;
            cube = InitCube(cubeSize);

        }
        /// <summary>
        /// Constructs color cube with given size
        /// </summary>
        /// <param name="size"></param>
        public GenericColorCube(int size)
        {
            cubeSize = size;
            cube = InitCube(cubeSize);

        }
		/// <summary>
        /// Finds list of nearest colors.
        /// </summary>
        /// <param name="color">Target colors</param>
        /// <returns></returns>
        public List<Color> Find(Color color)
        {
            List<Color> result = new List<Color>();
            double size = cubeSize;
            int r1 = (int)Math.Floor(color.R / (256 / size));
            int g1 = (int)Math.Floor(color.G / (256 / size));
            int b1 = (int)Math.Floor(color.B / (256 / size));

            int r2 = (int)Math.Ceiling(color.R / (256 / size));
            int g2 = (int)Math.Ceiling(color.G / (256 / size));
            int b2 = (int)Math.Ceiling(color.B / (256 / size));

            for (int r=0;r<size;r++)
            {
                for (int i = r1; i < r2; i++)
                    for (int j = g1; j < g2; j++)
                        for (int k = b1; k < b2; k++)
                            if (cube[i][j][k].Count>0)
                                result.AddRange(cube[i][j][k].Colors.Keys);
                if (result.Count > 0) break;
                if (r1>0)r1--;
                if (g1>0)g1--;
                if (b1>0)b1--;
                int max = cubeSize;
                if (r2<max)r2++;
                if (g2<max)g2++;
                if (b2<max)b2++;
            }
            return result;
        }
        /// <summary>
        /// Get safely a color list from cube and add colors ro result list.
        /// </summary>
        /// <param name="r">Red index</param>
        /// <param name="g">Green index</param>
        /// <param name="b">Blue index</param>
        /// <param name="result">Result list</param>
        /// <returns>true if color was within cube, otherwise false</returns>
        private bool AddIfSafe(int r, int g, int b, Dictionary<Color,T> result)
        {
            if (r < 0 || g < 0 || b < 0 ||
                r >= cubeSize ||
                g >= cubeSize ||
                b >= cubeSize)
                return false;
			if (cube[r][g][b].Count == 0) return false;
			foreach (KeyValuePair<Color, T> p in cube[r][g][b].Colors)
                if (!result.ContainsKey(p.Key)) 
                    result.Add(p.Key, p.Value);
            return true;
        }
		/// <summary>
		/// Get safely a color list from cube and add colors ro result list.
		/// </summary>
		/// <param name="r">Red index</param>
		/// <param name="g">Green index</param>
		/// <param name="b">Blue index</param>
		/// <param name="result">Result cube</param>
		/// <returns>true if color was within cube, otherwise false</returns>
		private bool AddIfSafe(int r, int g, int b, GenericColorCube<T> result)
		{
			if (r < 0 || g < 0 || b < 0 ||
				r >= cubeSize ||
				g >= cubeSize ||
				b >= cubeSize)
				return false;
			if (cube[r][g][b].Count == 0) return false;
			foreach (KeyValuePair<Color, T> p in cube[r][g][b].Colors)
				if (!result.ContainsKey(p.Key))
					result.Add(p.Key, p.Value);
			return true;
		}

		public Dictionary<Color,T> FindMore(Color color, int margin)
        {
            Dictionary<Color,T> result = new Dictionary<Color,T>();
            double size = cubeSize;

            int r1 = (int)Math.Floor(color.R / (256 / size));
            int g1 = (int)Math.Floor(color.G / (256 / size));
            int b1 = (int)Math.Floor(color.B / (256 / size));

            int r2 = (int)Math.Ceiling(color.R / (256 / size));
            int g2 = (int)Math.Ceiling(color.G / (256 / size));
            int b2 = (int)Math.Ceiling(color.B / (256 / size));

            for (int r = 0; r < size; r++)
            {
                Dictionary<Color,T> res = new Dictionary<Color,T>();
                for (int i = r1; i <= r2; i++)
                    for (int j = g1; j <= g2; j++)
                    {
                        AddIfSafe(i, j, b1, res);
                        if (b2!= b1) AddIfSafe(i, j, b2, res);
                    }
                // skip top and bottom blue planes
                for (int i = r1; i <= r2; i++)
                    for (int k = b1+1; k < b2; k++)
                    {
                        AddIfSafe(i, g1, k, res);
                        if (g1!=g2) AddIfSafe(i, g2, k, res);
                    }
                // skip top and bottom blue and green 
                for (int j = g1+1; j < g2; j++)
                    for (int k = b1 + 1; j < b2; j++)
                    {
                        AddIfSafe(r1, j, k, res);
                        if (r1!=r2) AddIfSafe(r2, j, k, res);
                    }
                foreach (KeyValuePair<Color, T> p in res)
                    if (!result.ContainsKey(p.Key))
                        result.Add(p.Key, p.Value);
                if (result.Count > 0) margin--;
                if (margin <= 0) break ;

                r1--; g1--; b1--;
                r2++; g2++; b2++;
            }
            return result;
        }

        public void Add(Color color)
        {
            Add(color, default(T));
        }
		public void Add(Color color, T o)
		{
			int r = (color.R / (256 / cubeSize));
			int g = (color.G / (256 / cubeSize));
			int b = (color.B / (256 / cubeSize));
			cube[r][g][b].Add(color, o);
			count++;
		}
		public bool Remove(Color color)
		{
			int r = (color.R / (256 / cubeSize));
			int g = (color.G / (256 / cubeSize));
			int b = (color.B / (256 / cubeSize));
			bool removed = cube[r][g][b].Remove(color);
			if (removed) count--;
			return removed;
		}
		/// <summary>
        /// Get safely a color list from cube and add colors ro result list.
        /// </summary>
        /// <param name="r">Red</param>
        /// <param name="g">Green</param>
        /// <param name="b">Blue</param>
        /// <param name="result">Result list</param>
        /// <returns>true if color was within cube, otherwise false</returns>
        private bool AddIfSafe(int r, int g, int b, Dictionary<Color,object> result)
        {
            if (r < 0 || g < 0 || b < 0 ||
                r >= cubeSize ||
                g >= cubeSize ||
                b >= cubeSize)
                return false;
            foreach (KeyValuePair<Color,T> p in cube[r][g][b].Colors)
                if (!result.ContainsKey(p.Key)) 
                    result.Add(p.Key, p.Value);
            return true;
        }

		public bool ContainsKey(Color c)
		{
			int r = (c.R / (256 / cubeSize));
			int g = (c.G / (256 / cubeSize));
			int b = (c.B / (256 / cubeSize));
			return cube[r][g][b].ContainsKey(c);

		}
		public void Clear()
		{
			for (int i = 0; i < cubeSize; i++)
			{
				for (int j = 0; j < cubeSize; j++)
				{
					for (int k = 0; k < cubeSize; k++)
					{
						cube[i][j][k].Clear();
					}
				}
			}
			count = 0;
		}

		public List<Color> GetColors()
		{
			List<Color> l = new List<Color>();
			for (int i = 0; i < cubeSize; i++)
			{
				for (int j = 0; j < cubeSize; j++)
				{
					for (int k = 0; k < cubeSize; k++)
					{
						ColorNode cn = cube[i][j][k];
						if (cn!=null) l.AddRange(cn.Colors.Keys);
					}
				}
			}
			return l;
		}
		public void Replicate(GenericColorCube<T> other)
		{
			count = 0;
			for (int i = 0; i < cubeSize; i++)
			{
				for (int j = 0; j < cubeSize; j++)
				{
					for (int k = 0; k < cubeSize; k++)
					{
						ColorNode cn = other.cube[i][j][k];
						if (cn.Count == 0)
						{
							cube[i][j][k].Clear();
						}
						else
						{
							cube[i][j][k].InitColors(cn.Colors);
							count += cn.Count;
						}
					}
				}
			}
		}

        public List<List<List<ColorNode>>> InitCube(int size)
        {
            List<List<List<ColorNode>>>  newcube = new List<List<List<ColorNode>>>(size);
            for (int i = 0; i < size; i++)
            {
                newcube.Add(new List<List<ColorNode>>(size));
                for (int j = 0; j < size; j++)
                {
                    newcube[i].Add(new List<ColorNode>(size));
                    for (int k = 0; k < size; k++)
                        newcube[i][j].Add(new ColorNode());
                }
            }
            return newcube;
        }
		protected ColorNode GetNode(Color c)
		{
			int r = (c.R / (256 / cubeSize));
			int g = (c.G / (256 / cubeSize));
			int b = (c.B / (256 / cubeSize));
			return cube[r][g][b];
		}
		public bool TryGetValue(Color c, ref T o)
		{
			int r = (c.R / (256 / cubeSize));
			int g = (c.G / (256 / cubeSize));
			int b = (c.B / (256 / cubeSize));
			return cube[r][g][b].TryGetValue(c,ref o);
		}

		public T this[Color c]
		{
			get
			{
				T t = default(T);
				if (TryGetValue(c, ref t)) return t;
				System.Diagnostics.Debug.Assert(false, "Key color not found in ColorCube");
				return t;
			}
		}
		#region IEnumerable<KeyValuePair<Color,T>> Members

		public IEnumerator<KeyValuePair<Color, T>> GetEnumerator()
		{
			for (int i = 0; i < cubeSize; i++)
			{
				for (int j = 0; j < cubeSize; j++)
				{
					for (int k = 0; k < cubeSize; k++)
					{
						ColorNode cn = cube[i][j][k];
						if (cn.Count == 0) continue;
						foreach (KeyValuePair<Color, T> p in cn.Colors)
							yield return p;
					}
				}
			}
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		#region ICloneable Members

		public object Clone()
		{
			GenericColorCube<T> newcube = new GenericColorCube<T>(cubeSize);
			newcube.count = count;
			for (int i = 0; i < cubeSize; i++)
			{
				for (int j = 0; j < cubeSize; j++)
				{
					for (int k = 0; k < cubeSize; k++)
					{
						ColorNode cn = cube[i][j][k];
						if (cn.Count == 0) continue;
						newcube.cube[i][j][k].InitColors(cn.Colors);
					}
				}
			}
			
			return newcube;
		}
		#endregion

	}

	public class ColorCube : GenericColorCube<bool>
	{
		public ColorCube() : base()
		{
		}
		public ColorCube(int size)
			: base(size)
		{
		}
	}

}
